package app

import (
	"bytes"
	"compress/zlib"
	"encoding/base64"
	"io"
	"net/http"
	"strconv"
	"time"

	"gitee.com/leicc/go-orm/log"
	"github.com/gorilla/websocket"
)

const (
	writeWait  = 10 * time.Second
	pongWait   = 60 * time.Second
	pingPeriod = (pongWait * 9) / 10
	sendChanSize   = 512
	wsBufferSize   = 2048
	maxMessageSize = 4096
)

type Client struct {
	State *Agent
	conn *websocket.Conn
	ChSend chan []byte
}

type FuncPacketHandler func(c *Client, msgType int, message []byte)

var (
	upgrader = websocket.Upgrader{
		ReadBufferSize:  wsBufferSize,
		WriteBufferSize: wsBufferSize,
		CheckOrigin: func(r *http.Request) bool {//允许跨域请求
			return true
		},
	}
	//设置注册信息回调处理业务逻辑
	doPacketHandler FuncPacketHandler = nil
)

//注册业务处理逻辑的回调函数业务
func RegisterPacketHandler(handler FuncPacketHandler) {
	doPacketHandler = handler
}

//客户端执行关闭的操作逻辑
func (c *Client) doClose() {
	defer func() {
		if err := recover(); err != nil {
			log.Write(log.ERROR,  err)
		}
	}()
	c.State.UnRegister(c)
	c.conn.Close()
	close(c.ChSend)
}

//客户端读数据的业务
func (c *Client) doReadService() {
	defer func() {
		c.doClose()
	}()
	c.conn.SetReadLimit(maxMessageSize)
	c.conn.SetReadDeadline(time.Now().Add(pongWait))
	c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
	for {
		msgType, message, err := c.conn.ReadMessage()
		if err != nil {
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
				log.Write(log.ERROR, err)
			}
			break
		}
		//解压缩处理业务逻辑
		if bstr := c.DeCompress(message); bstr != nil && len(bstr) > 0 {
			message = bstr //复制解压缩之后的数据
		}
		go doPacketHandler(c, msgType, message) //加工处理信息
	}
}

//数据压缩处理业务逻辑
func (c *Client) DeCompress(str []byte) []byte {
	nidx := bytes.IndexByte(str, '$')
	nsize, err := strconv.ParseInt(string(str[0:nidx]), 10, 64)
	if err != nil || nsize < 1 {//参数错误的情况
		log.Write(log.ERROR, "decompress data nsize", err)
		return nil
	}
	bstr  := make([]byte, len(str))
	nlen, err := base64.StdEncoding.Decode(bstr, str[nidx+1:])
	if err != nil {
		log.Write(log.ERROR, "decompress data error", err)
		return nil
	}
	bstr  = bstr[0:nlen]
	var buf bytes.Buffer
	rd   := bytes.NewReader(bstr)
	zrer, err := zlib.NewReader(rd)
	if err != nil {
		log.Write(log.ERROR, "zlib.NewReader data error", err)
		return nil
	}
	defer zrer.Close()
	io.Copy(&buf, zrer)
	return buf.Bytes()
}

//往管道写数据 发送私人消息
func (c *Client) Send(message []byte) {
	defer func() {
		if err := recover(); err != nil {
			log.Write(log.ERROR, err)
		}
	}()
	select {
	case c.ChSend <- message:
	default://执行失败的情况执行默认
		c.doClose()
	}
}

//客户端写数据的业务
func (c *Client) doWriteService() {
	ticker := time.NewTicker(pingPeriod)
	defer func() {
		ticker.Stop()
		c.doClose()
	}()
	for {
		select {
		case message, ok := <-c.ChSend:
			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if !ok {
				c.conn.WriteMessage(websocket.CloseMessage, []byte{})
				return
			}
			w, err := c.conn.NextWriter(websocket.TextMessage)
			if err != nil {
				return
			}
			w.Write(message)
			n := len(c.ChSend)
			for i := 0; i < n; i++ {
				w.Write(<-c.ChSend)
			}
			if err := w.Close(); err != nil {
				return
			}
		case <-ticker.C:
			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
				log.Write(log.ERROR, err)
				return
			}
		}
	}
}

//客户端连接之后 启动业务服务
func DoClientService(agent *Agent, w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Write(log.ERROR, err)
		return
	}
	client := &Client{State: agent, conn: conn, ChSend: make(chan []byte, sendChanSize)}
	agent.Register(0, client) //注册到状态机管理当中
	go client.doWriteService()
	go client.doReadService()
}
